package pt.inescid.components.policyengine.heimdall.pdp.base;

import java.util.Vector;

import pt.inescid.components.policyengine.heimdall.base.EventObj;
import pt.inescid.components.policyengine.heimdall.pap.base.QuantifierDefinition;
import pt.inescid.components.policyengine.heimdall.pdp.PolicyDecision;

public class QuantifierInstance {

	private CesInstance ces;
	private QuantifierDefinition definition;
	private QuantifierInstance nextQt;
	private PolicyInstance qtPolicy;
	
	boolean tryCache;
	int numberOfMatches;

	private PolicyDecision decision;
	
	EventObj currentEvent = null;

	private RuleInstance restriction;


	//  ----------------------------------------------------------
//  ------------------------   constructor   ------------------------
//  ----------------------------------------------------------
	public QuantifierInstance() {
	}

	public QuantifierInstance(QuantifierDefinition definition, 
			ModelInstance currentModel, 
			PolicyInstance qtPolicy) {
		this.definition = definition;
		this.nextQt = null;
		this.qtPolicy = qtPolicy;
		this.decision = new PolicyDecision();
		this.numberOfMatches = 0;
		this.tryCache = true;
		
		this.restriction = RuleInstance.getInstance(
				definition.getRestriction(), currentModel,
				qtPolicy);

		CesInstance newCes = new CesInstance(definition, 
				currentModel, qtPolicy);
		ces = newCes;
		currentModel.addCes(newCes);
		
	}

	
	
	
	
	/* ===========================================================
	==============================================================
	=============                    =============================
	=============      hasThisVariable       =============================
	=============                    =============================
	==============================================================
	=========================================================== */
	public boolean hasThisVariable(String eventField) {

		String thisVariable = definition.getName();
		String eventFieldVariable = eventField.substring(0,
				eventField.indexOf("."));
		
		if (thisVariable.equals(eventFieldVariable)) {
			return (true);
		}
		else {
			return (false);
		}		
	} // hasThisVariable

	
	

	/* ===========================================================
	==============================================================
	=============                    =============================
	=============      isRelevant       =============================
	=============                    =============================
	==============================================================
	=========================================================== */
	public boolean isRelevant(ModelInstance currentModel) {
		if (restriction == null) {
			return (true);
		}
		else {
			return (((Boolean) restriction.evaluate(currentModel,
					false)).
					booleanValue());
		}
	} // isRelevant


	
	
	
	
	
	//  ----------------------------------------------------------
//  ------------------------   evaluate   ------------------------
//  ----------------------------------------------------------
	public void evaluate(ModelInstance currentModel) {
		
		// for the FOR_ALL and the EXACTLY rules we
		// can't use the cache.
		// Otherwise we give it a first try
		if (decision.getStatus() != 
			PolicyDecision.STATUS_UNDEFINED &&
			
			(definition.getType() == 
			QuantifierDefinition.FOR_ALL_QUANTIFIER ||
			definition.getType() ==
				QuantifierDefinition.EXACTLY_QUANTIFIER)) {
			
			tryCache = false;
		}
		else {
			tryCache = true;
		}

		
		Vector allUnprocEvents = ces.getUnprocessedEvents(
				currentModel, tryCache);
		decision = ces.getDomain().getDecision();

		// add current leased events combination
		Vector allLeaseEvents = currentModel.getCurrentLeaseEvents();
		EventObj currentLeaseEvent = null;
		if (allLeaseEvents != null) {
			for (int i = 0; i < allLeaseEvents.size(); i++) {
				currentLeaseEvent = (EventObj) allLeaseEvents.elementAt(i);
				
				if (ces.isRelevant(currentLeaseEvent, currentModel)) {
					allUnprocEvents.add(currentLeaseEvent);
				}
			}
		}
		
		PolicyDecision currentDecision = null;
		for (int i = 0; i < allUnprocEvents.size(); i++) {
			
			currentEvent = (EventObj) allUnprocEvents.elementAt(i);
			
			// check if events are within quantifier time limits
			if (isRelevant(currentModel)) {
				
				if (nextQt != null) {
					nextQt.evaluate(currentModel);
					currentDecision = nextQt.getDecision();
				}
				else {
					qtPolicy.evaluateRule(currentModel, tryCache);
					currentDecision = qtPolicy.getDecision();
				}
				
				if (
//						currentDecision.getStatus() ==
//						PolicyDecision.STATUS_FINAL &&
						currentDecision.getValue() ==
							PolicyDecision.DECISION_AUTHORIZE) {
					
					numberOfMatches++;
				}
				
				switch (definition.getType()) {
//				----------------------------------------------------------
//				------------------------   AT_LEAST_QUANTIFIER   ------------------------
//				----------------------------------------------------------
				case QuantifierDefinition.AT_LEAST_QUANTIFIER:
					if (numberOfMatches >= definition.getValue()) {
						decision.setStatus(PolicyDecision.STATUS_FINAL);
						decision.setValue(
								PolicyDecision.DECISION_AUTHORIZE);
						
						return;
					}
					break;
					
					
//					----------------------------------------------------------
//					------------------------   AT_MOST_QUANTIFIER   ------------------------
//					----------------------------------------------------------
				case QuantifierDefinition.AT_MOST_QUANTIFIER:
					if (numberOfMatches > definition.getValue()) {
						decision.setStatus(PolicyDecision.STATUS_FINAL);
						decision.setValue(
								PolicyDecision.DECISION_DENY);
						
						return;
					}
					break;
					
					
				default:
					System.out.println("Tipo de quantificador desconhecido: " + 
							definition.getType());
				System.exit(-1);
				}
			} // if (isRelevant(currentModel)) {
		} // for (int i = 0; i < allUnprocEvents.size(); i++)
		
		
		// if we used the cache
		// and no definitive decision was
		// found, evaluate with all events.
		if (tryCache) {
			tryCache = false;
			numberOfMatches = 0;
			allUnprocEvents = ces.getUnprocessedEvents(
					currentModel, tryCache);
			decision = ces.getDomain().getDecision();

			// add current leased events combination
			allLeaseEvents = currentModel.getCurrentLeaseEvents();
			if (allLeaseEvents != null) {
				for (int i = 0; i < allLeaseEvents.size(); i++) {
					currentLeaseEvent = (EventObj) allLeaseEvents.elementAt(i);
					
					if (ces.isRelevant(currentLeaseEvent, currentModel)) {
						allUnprocEvents.add(currentLeaseEvent);
					}
				}
			}
			
//			if (decision.getStatus() ==
//						PolicyDecision.STATUS_FINAL) {
				for (int i = 0; i < allUnprocEvents.size(); i++) {
					
					currentEvent = (EventObj) allUnprocEvents.elementAt(i);

					// check if events are within quantifier time limits
					if (isRelevant(currentModel)) {
						
						if (nextQt != null) {
							nextQt.evaluate(currentModel);
							currentDecision = nextQt.getDecision();
						}
						else {
							qtPolicy.evaluateRule(currentModel, tryCache);
							currentDecision = qtPolicy.getDecision();
						}
						
						if (currentDecision.getValue() ==
							PolicyDecision.DECISION_AUTHORIZE) {
							
							numberOfMatches++;
						}
						
						
						switch (definition.getType()) {
//						----------------------------------------------------------
//						------------------------   AT_LEAST_QUANTIFIER   ------------------------
//						----------------------------------------------------------
						case QuantifierDefinition.AT_LEAST_QUANTIFIER:
							if (numberOfMatches >= definition.getValue()) {
								decision.setStatus(PolicyDecision.STATUS_FINAL);
								decision.setValue(
										PolicyDecision.DECISION_AUTHORIZE);
								
								return;
							}
							break;
							
							
							
//							----------------------------------------------------------
//							------------------------   AT_MOST_QUANTIFIER   ------------------------
//							----------------------------------------------------------
						case QuantifierDefinition.AT_MOST_QUANTIFIER:
							if (numberOfMatches > definition.getValue()) {
								decision.setStatus(PolicyDecision.STATUS_FINAL);
								decision.setValue(
										PolicyDecision.DECISION_DENY);
								
								return;
							}
							break;
							
							
						default:
							System.out.println("Tipo de quantificador desconhecido: " + 
									definition.getType());
						System.exit(-1);
						}		
					} // if (isRelevant(currentModel)) {
				} // for (int i = 0; i < allUnprocEvents.size(); i++)
//			} // if (decision.getStatus() == ...STATUS_FINAL) {
		} // if (onlyCached)
		

		
		// After reviewing all unprocessed events
		// and if there are no more events to retrieve
		if (decision.getStatus() ==
			PolicyDecision.STATUS_FINAL) {
		
			switch (definition.getType()) {
//			----------------------------------------------------------
//			------------------------   AT_LEAST_QUANTIFIER   ------------------------
//			----------------------------------------------------------
			case QuantifierDefinition.AT_LEAST_QUANTIFIER:
				decision.setStatus(PolicyDecision.STATUS_FINAL);
				decision.setValue(
						PolicyDecision.DECISION_DENY);
				
				return;

				
//				----------------------------------------------------------
//				------------------------   AT_MOST_QUANTIFIER   ------------------------
//				----------------------------------------------------------
			case QuantifierDefinition.AT_MOST_QUANTIFIER:
				if (numberOfMatches > definition.getValue()) {
					decision.setStatus(PolicyDecision.STATUS_FINAL);
					decision.setValue(
							PolicyDecision.DECISION_DENY);
					
					return;
				}
				break;
				
			
			default:
				System.out.println("Tipo de quantificador desconhecido: " + 
						definition.getType());
			System.exit(-1);
			}	
		} // if (decision.getStatus() == ...STATUS_FINAL) {
	} // evaluate;

	
	
	
//  ----------------------------------------------------------
//  ------------------------   toString   ------------------------
//  ----------------------------------------------------------
	public String toString() {
		String result = "";
		
		result += "Qt " + definition.getName() + ":\n";
		result += "\tCes:\n" + ces + "\n";
		
		return (result);
	}

	public CesInstance getCes() {
		return ces;
	}

	public QuantifierDefinition getDefinition() {
		return definition;
	}

	public void setCes(CesInstance ces) {
		this.ces = ces;
	}

	
	
	public void setNextQt(QuantifierInstance nextQt) {
		this.nextQt = nextQt;
	}

	public PolicyDecision getDecision() {
		return decision;
	}

	public void setDecision(PolicyDecision decision) {
		this.decision = decision;
	}

	public EventObj getCurrentEvent() {
		return currentEvent;
	}

}; // QuantifierDefinition
